#include "UnityCG.cginc"
#include "VertexCommon.hlsl"
#include "ZBufferCommon.hlsl"

sampler2D _MainTex;

float Radius = 0.6;
float Intensity = 1;
float Bias = 1.e-4;
const static float K = 1;
int NumSamples = 10;

const static float Epsilon = 0.0001;

float Random(float u, float v) {
    float f = dot(float2(12.9898, 78.233), float2(u, v));
    return frac(43758.5453 * sin(f));
}

float2 DiskMapping(float2 xi) {
	float2 uOffset = 2 * xi - 1;
	if (uOffset.x == 0 && uOffset.y == 0)
		return uOffset;
	float theta = 0;
	float r = 0;
	if (abs(uOffset.x) > abs(uOffset.y)) {
		r = uOffset.x;
		theta = (uOffset.y / uOffset.x) * UNITY_PI / 4;
	} else {
		r = uOffset.y;
		theta = UNITY_PI / 2 - (uOffset.x / uOffset.y) * UNITY_PI / 4;
	}
	float2 u = 0;
	sincos(theta, u.x, u.y);
	return r * u.yx;
}

// Check if the camera is perspective.
// (returns 1.0 when orthographic)
float CheckPerspective(float x) {
    return lerp(x, 1.0, unity_OrthoParams.w);
}

// Reconstruct view-space position from UV and depth.
// p11_22 = (unity_CameraProjection._11, unity_CameraProjection._22)
// p13_31 = (unity_CameraProjection._13, unity_CameraProjection._23)
float3 ReconstructViewPos(float2 uv, float depth) {
    float2 p11_22 = float2(unity_CameraProjection._11, unity_CameraProjection._22);
    float2 p13_31 = float2(unity_CameraProjection._13, unity_CameraProjection._23);
    return float3((uv * 2.0 - 1.0 - p13_31) / p11_22 * CheckPerspective(depth), depth);
}

float2 ViewToImage(float3 vPos) {
	float3x3 proj = (float3x3)unity_CameraProjection;
	float3 uv = mul(unity_CameraProjection, vPos);
	return (uv.xy + 1)/2;
}

float4 frag(FS_IN i) : SV_Target {
    float3 vNorm;
    float vDepth = GetDepthNormal(i.uv, vNorm);
	float3 vPos = ReconstructViewPos(i.uv, vDepth);

	float AO = 0;
	for (int si = 0; si < NumSamples; si++) {
		float2 xi = 0;
		xi.x = Random(i.uv.x, i.uv.y + si);
		xi.y = Random(i.uv.x + si, i.uv.y);
		float2 uv = DiskMapping(xi);
		float3 vPosSample = 0;
		vPosSample.xy = uv * Radius + vPos.xy;
		vPosSample.z = vPos.z;
		float2 uvSample = ViewToImage(vPosSample);
		if (0 <= uvSample.x && uvSample.x <= 1 && 0 <= uvSample.y && uvSample.y <= 1) {
			float3 vNormSample;
			float vDepthSample = GetDepthNormal(uvSample, vNormSample);
			vPosSample = ReconstructViewPos(uvSample, vDepthSample);
			float3 V = vPosSample - vPos;
			float dis = length(V);
			if (dis < Radius) {
				AO += max(0, dot(V, vNorm) - vDepth * Bias) / (pow(dis, 2) + Epsilon);
			}
		}
	}
	AO = 1 - 2 * Intensity * AO / NumSamples;
	AO = pow(max(0, AO), K);
	return float4(AO, AO, AO, 1);
}